msg_tool\scripts\bgi\archive/
v1.rs

1//! Buriko General Interpreter/Ethornell Archive File Version 1 (.arc)
2use super::bse::*;
3use super::dsc::*;
4use crate::ext::io::*;
5use crate::ext::mutex::*;
6use crate::scripts::base::*;
7use crate::types::*;
8use crate::utils::encoding::encode_string;
9use crate::utils::struct_pack::*;
10use crate::utils::threadpool::*;
11use anyhow::Result;
12use msg_tool_macro::*;
13use std::collections::HashMap;
14use std::io::{Read, Seek, SeekFrom, Write};
15use std::ops::DerefMut;
16use std::sync::{Arc, Mutex};
17
18#[derive(Debug)]
19/// Builder for BGI Archive Version 1 scripts.
20pub struct BgiArchiveBuilder {}
21
22impl BgiArchiveBuilder {
23    /// Creates a new instance of `BgiArchiveBuilder`.
24    pub const fn new() -> Self {
25        BgiArchiveBuilder {}
26    }
27}
28
29impl ScriptBuilder for BgiArchiveBuilder {
30    fn default_encoding(&self) -> Encoding {
31        Encoding::Cp932
32    }
33
34    fn default_archive_encoding(&self) -> Option<Encoding> {
35        Some(Encoding::Cp932)
36    }
37
38    fn build_script(
39        &self,
40        data: Vec<u8>,
41        filename: &str,
42        _encoding: Encoding,
43        archive_encoding: Encoding,
44        config: &ExtraConfig,
45        _archive: Option<&Box<dyn Script>>,
46    ) -> Result<Box<dyn Script + Send + Sync>> {
47        Ok(Box::new(BgiArchive::new(
48            MemReader::new(data),
49            archive_encoding,
50            config,
51            filename,
52        )?))
53    }
54
55    fn build_script_from_file(
56        &self,
57        filename: &str,
58        _encoding: Encoding,
59        archive_encoding: Encoding,
60        config: &ExtraConfig,
61        _archive: Option<&Box<dyn Script>>,
62    ) -> Result<Box<dyn Script + Send + Sync>> {
63        if filename == "-" {
64            let data = crate::utils::files::read_file(filename)?;
65            Ok(Box::new(BgiArchive::new(
66                MemReader::new(data),
67                archive_encoding,
68                config,
69                filename,
70            )?))
71        } else {
72            let f = std::fs::File::open(filename)?;
73            let reader = std::io::BufReader::new(f);
74            Ok(Box::new(BgiArchive::new(
75                reader,
76                archive_encoding,
77                config,
78                filename,
79            )?))
80        }
81    }
82
83    fn build_script_from_reader<'a>(
84        &self,
85        reader: Box<dyn ReadSeek + Send + Sync + 'a>,
86        filename: &str,
87        _encoding: Encoding,
88        archive_encoding: Encoding,
89        config: &ExtraConfig,
90        _archive: Option<&Box<dyn Script>>,
91    ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
92        Ok(Box::new(BgiArchive::new(
93            reader,
94            archive_encoding,
95            config,
96            filename,
97        )?))
98    }
99
100    fn extensions(&self) -> &'static [&'static str] {
101        &["arc"]
102    }
103
104    fn script_type(&self) -> &'static ScriptType {
105        &ScriptType::BGIArcV1
106    }
107
108    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
109        if buf_len >= 12 && buf.starts_with(b"PackFile    ") {
110            return Some(10);
111        }
112        None
113    }
114
115    fn is_archive(&self) -> bool {
116        true
117    }
118
119    fn create_archive(
120        &self,
121        filename: &str,
122        files: &[&str],
123        encoding: Encoding,
124        config: &ExtraConfig,
125    ) -> Result<Box<dyn Archive>> {
126        let f = std::fs::File::create(filename)?;
127        let writer = std::io::BufWriter::new(f);
128        Ok(Box::new(BgiArchiveWriter::new(
129            writer, files, encoding, config,
130        )?))
131    }
132}
133
134#[derive(Clone, Debug, StructPack, StructUnpack)]
135struct BgiFileHeader {
136    #[fstring = 16]
137    filename: String,
138    offset: u32,
139    size: u32,
140    #[fvec = 8]
141    _padding: Vec<u8>,
142}
143
144#[derive(Debug)]
145struct Entry<T: Read + Seek + std::fmt::Debug> {
146    header: BgiFileHeader,
147    reader: Arc<Mutex<T>>,
148    pos: usize,
149    base_offset: u64,
150    script_type: Option<ScriptType>,
151}
152
153impl<T: Read + Seek + std::fmt::Debug + Send + Sync> ArchiveContent for Entry<T> {
154    fn name(&self) -> &str {
155        &self.header.filename
156    }
157
158    fn size(&self) -> Option<u64> {
159        Some(self.header.size as u64)
160    }
161
162    fn script_type(&self) -> Option<&ScriptType> {
163        self.script_type.as_ref()
164    }
165
166    fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
167        Ok(Box::new(self))
168    }
169}
170
171impl<T: Read + Seek + std::fmt::Debug> Read for Entry<T> {
172    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
173        let mut reader = self.reader.lock().map_err(|e| {
174            std::io::Error::new(
175                std::io::ErrorKind::Other,
176                format!("Failed to lock mutex: {}", e),
177            )
178        })?;
179        reader.seek(SeekFrom::Start(
180            self.base_offset + self.header.offset as u64 + self.pos as u64,
181        ))?;
182        let bytes_read = buf.len().min(self.header.size as usize - self.pos);
183        if bytes_read == 0 {
184            return Ok(0);
185        }
186        let bytes_read = reader.read(&mut buf[..bytes_read])?;
187        self.pos += bytes_read;
188        Ok(bytes_read)
189    }
190}
191
192impl<T: Read + Seek + std::fmt::Debug> Seek for Entry<T> {
193    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
194        let new_pos = match pos {
195            SeekFrom::Start(offset) => offset as usize,
196            SeekFrom::End(offset) => {
197                if offset < 0 {
198                    if (-offset) as usize > self.header.size as usize {
199                        return Err(std::io::Error::new(
200                            std::io::ErrorKind::InvalidInput,
201                            "Seek from end exceeds file length",
202                        ));
203                    }
204                    self.header.size as usize - (-offset) as usize
205                } else {
206                    self.header.size as usize + offset as usize
207                }
208            }
209            SeekFrom::Current(offset) => {
210                if offset < 0 {
211                    if (-offset) as usize > self.pos {
212                        return Err(std::io::Error::new(
213                            std::io::ErrorKind::InvalidInput,
214                            "Seek from current exceeds current position",
215                        ));
216                    }
217                    self.pos.saturating_sub((-offset) as usize)
218                } else {
219                    self.pos + offset as usize
220                }
221            }
222        };
223        self.pos = new_pos;
224        Ok(self.pos as u64)
225    }
226
227    fn stream_position(&mut self) -> std::io::Result<u64> {
228        Ok(self.pos as u64)
229    }
230}
231
232#[derive(Debug)]
233/// Buriko General Interpreter/Ethornell Archive File Version 1 (.arc)
234pub struct BgiArchive<'a, T: Read + Seek + std::fmt::Debug + 'a> {
235    reader: Arc<Mutex<T>>,
236    entries: Vec<BgiFileHeader>,
237    base_offset: u64,
238    #[cfg(feature = "bgi-img")]
239    is_sysgrp_arc: bool,
240    _mark: std::marker::PhantomData<&'a ()>,
241}
242
243impl<'b, T: Read + Seek + std::fmt::Debug + 'b> BgiArchive<'b, T> {
244    /// Creates a new BGI archive from a reader.
245    ///
246    /// * `reader` - The reader to read the archive from.
247    /// * `archive_encoding` - The encoding used for the archive.
248    /// * `config` - Extra configuration options.
249    /// * `filename` - The name of the archive file.
250    pub fn new(
251        mut reader: T,
252        archive_encoding: Encoding,
253        _config: &ExtraConfig,
254        _filename: &str,
255    ) -> Result<Self> {
256        let mut header = [0u8; 12];
257        reader.read_exact(&mut header)?;
258        if !header.starts_with(b"PackFile    ") {
259            return Err(anyhow::anyhow!("Invalid BGI archive header"));
260        }
261
262        let file_count = reader.read_u32()?;
263        let mut entries = Vec::with_capacity(file_count as usize);
264        for _ in 0..file_count {
265            let entry = BgiFileHeader::unpack(&mut reader, false, archive_encoding, &None)?;
266            entries.push(entry);
267        }
268
269        #[cfg(feature = "bgi-img")]
270        let is_sysgrp_arc = _config.bgi_is_sysgrp_arc.unwrap_or_else(|| {
271            std::path::Path::new(&_filename.to_lowercase())
272                .file_name()
273                .map(|f| f == "sysgrp.arc")
274                .unwrap_or(false)
275        });
276
277        Ok(BgiArchive {
278            reader: Arc::new(Mutex::new(reader)),
279            entries,
280            base_offset: 16 + (file_count as u64 * 32),
281            #[cfg(feature = "bgi-img")]
282            is_sysgrp_arc,
283            _mark: std::marker::PhantomData,
284        })
285    }
286}
287
288impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for BgiArchive<'b, T> {
289    fn default_output_script_type(&self) -> OutputScriptType {
290        OutputScriptType::Json
291    }
292
293    fn default_format_type(&self) -> FormatOptions {
294        FormatOptions::None
295    }
296
297    fn is_archive(&self) -> bool {
298        true
299    }
300
301    fn iter_archive_filename<'a>(
302        &'a self,
303    ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
304        Ok(Box::new(
305            self.entries.iter().map(|e| Ok(e.filename.clone())),
306        ))
307    }
308
309    fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
310        Ok(Box::new(self.entries.iter().map(|e| Ok(e.offset as u64))))
311    }
312
313    fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
314        if index >= self.entries.len() {
315            return Err(anyhow::anyhow!(
316                "Index out of bounds: {} (max: {})",
317                index,
318                self.entries.len()
319            ));
320        }
321        let entry = &self.entries[index];
322        let mut entry = Entry {
323            header: entry.clone(),
324            reader: self.reader.clone(),
325            pos: 0,
326            base_offset: self.base_offset,
327            script_type: None,
328        };
329        let mut buf = [0u8; 32];
330        match entry.read(&mut buf) {
331            Ok(_) => {}
332            Err(e) => {
333                return Err(anyhow::anyhow!(
334                    "Failed to read entry '{}': {}",
335                    entry.header.filename,
336                    e
337                ));
338            }
339        }
340        entry.pos = 0;
341        if buf.starts_with(b"DSC FORMAT 1.00") {
342            let data = match entry.data() {
343                Ok(data) => data,
344                Err(e) => {
345                    return Err(anyhow::anyhow!(
346                        "Failed to read DSC data for '{}': {}",
347                        entry.header.filename,
348                        e
349                    ));
350                }
351            };
352            entry.pos = 0;
353            let dsc = match DscDecoder::new(&data) {
354                Ok(dsc) => dsc,
355                Err(e) => {
356                    return Err(anyhow::anyhow!(
357                        "Failed to create DSC decoder for '{}': {}",
358                        entry.header.filename,
359                        e
360                    ));
361                }
362            };
363            let decoded = match dsc.unpack() {
364                Ok(decoded) => decoded,
365                Err(e) => {
366                    return Err(anyhow::anyhow!(
367                        "Failed to unpack DSC data for '{}': {}",
368                        entry.header.filename,
369                        e
370                    ));
371                }
372            };
373            let reader = MemReader::new(decoded);
374            if reader.data.starts_with(b"BSE 1.") {
375                match BseReader::new(reader, detect_script_type, &entry.header.filename) {
376                    Ok(bse_reader) => {
377                        return Ok(Box::new(bse_reader));
378                    }
379                    Err(e) => {
380                        return Err(anyhow::anyhow!(
381                            "Failed to create BSE reader for '{}': {}",
382                            entry.header.filename,
383                            e
384                        ));
385                    }
386                };
387            }
388            return Ok(Box::new(MemEntry {
389                name: entry.header.filename.clone(),
390                data: reader,
391                #[cfg(feature = "bgi-img")]
392                detect: if self.is_sysgrp_arc {
393                    detect_script_type_sysgrp
394                } else {
395                    detect_script_type
396                },
397                #[cfg(not(feature = "bgi-img"))]
398                detect: detect_script_type,
399            }));
400        }
401        if buf.starts_with(b"BSE 1.") {
402            let filename = entry.header.filename.clone();
403            #[cfg(feature = "bgi-img")]
404            let detect = if self.is_sysgrp_arc {
405                detect_script_type_sysgrp
406            } else {
407                detect_script_type
408            };
409            #[cfg(not(feature = "bgi-img"))]
410            let detect = detect_script_type;
411            match BseReader::new(entry, detect, &filename) {
412                Ok(mut bse_reader) => {
413                    if bse_reader.is_dsc() {
414                        let data = match bse_reader.data() {
415                            Ok(data) => data,
416                            Err(e) => {
417                                return Err(anyhow::anyhow!(
418                                    "Failed to read BSE data for '{}': {}",
419                                    &filename,
420                                    e
421                                ));
422                            }
423                        };
424                        let dsc = match DscDecoder::new(&data) {
425                            Ok(dsc) => dsc,
426                            Err(e) => {
427                                return Err(anyhow::anyhow!(
428                                    "Failed to create DSC decoder for '{}': {}",
429                                    &filename,
430                                    e
431                                ));
432                            }
433                        };
434                        let decoded = match dsc.unpack() {
435                            Ok(decoded) => decoded,
436                            Err(e) => {
437                                return Err(anyhow::anyhow!(
438                                    "Failed to unpack DSC data for '{}': {}",
439                                    &filename,
440                                    e
441                                ));
442                            }
443                        };
444                        let reader = MemReader::new(decoded);
445                        return Ok(Box::new(MemEntry {
446                            name: filename,
447                            data: reader,
448                            detect,
449                        }));
450                    }
451                    return Ok(Box::new(bse_reader));
452                }
453                Err(e) => {
454                    return Err(anyhow::anyhow!(
455                        "Failed to create BSE reader for '{}': {}",
456                        &filename,
457                        e
458                    ));
459                }
460            };
461        }
462        #[cfg(feature = "bgi-img")]
463        if self.is_sysgrp_arc {
464            entry.script_type = Some(ScriptType::BGIImg);
465        } else {
466            entry.script_type =
467                detect_script_type(&buf, buf.len(), &entry.header.filename).cloned();
468        }
469        #[cfg(not(feature = "bgi-img"))]
470        {
471            entry.script_type =
472                detect_script_type(&buf, buf.len(), &entry.header.filename).cloned();
473        }
474        Ok(Box::new(entry))
475    }
476}
477
478struct MemEntry<F: Fn(&[u8], usize, &str) -> Option<&'static ScriptType>> {
479    name: String,
480    data: MemReader,
481    detect: F,
482}
483
484impl<F: Fn(&[u8], usize, &str) -> Option<&'static ScriptType>> Read for MemEntry<F> {
485    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
486        self.data.read(buf)
487    }
488}
489
490impl<F: Fn(&[u8], usize, &str) -> Option<&'static ScriptType>> ArchiveContent for MemEntry<F> {
491    fn name(&self) -> &str {
492        &self.name
493    }
494
495    fn size(&self) -> Option<u64> {
496        Some(self.data.data.len() as u64)
497    }
498
499    fn script_type(&self) -> Option<&ScriptType> {
500        (self.detect)(&self.data.data, self.data.data.len(), &self.name)
501    }
502
503    fn data(&mut self) -> Result<Vec<u8>> {
504        Ok(self.data.data.clone())
505    }
506
507    fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
508        Ok(Box::new(&mut self.data))
509    }
510}
511
512fn detect_script_type(buf: &[u8], buf_len: usize, filename: &str) -> Option<&'static ScriptType> {
513    if buf_len >= 28 && buf.starts_with(b"BurikoCompiledScriptVer1.00\0") {
514        return Some(&ScriptType::BGI);
515    }
516    #[cfg(feature = "bgi-img")]
517    if buf_len >= 16 && buf.starts_with(b"CompressedBG___") {
518        return Some(&ScriptType::BGICbg);
519    }
520    #[cfg(feature = "bgi-audio")]
521    if buf_len >= 8 && buf[4..].starts_with(b"bw  ") {
522        return Some(&ScriptType::BGIAudio);
523    }
524    let filename = filename.to_lowercase();
525    if filename.ends_with("._bp") {
526        return Some(&ScriptType::BGIBp);
527    } else if filename.ends_with("._bsi") {
528        return Some(&ScriptType::BGIBsi);
529    }
530    None
531}
532
533#[cfg(feature = "bgi-img")]
534fn detect_script_type_sysgrp(
535    _buf: &[u8],
536    _buf_len: usize,
537    _filename: &str,
538) -> Option<&'static ScriptType> {
539    Some(&ScriptType::BGIImg)
540}
541
542/// BGI Archive Writer for Version 1
543pub struct BgiArchiveWriter<T: Write + Seek> {
544    writer: Arc<Mutex<T>>,
545    headers: Arc<Mutex<HashMap<String, BgiFileHeader>>>,
546    compress_file: bool,
547    encoding: Encoding,
548    compress_level: u8,
549    runner: ThreadPool<Result<()>>,
550}
551
552impl<T: Write + Seek> BgiArchiveWriter<T> {
553    /// Creates a new BGI Archive Writer.
554    ///
555    /// * `writer` - The writer to write the archive to.
556    /// * `files` - The list of files to include in the archive.
557    /// * `encoding` - The encoding used for the archive.
558    /// * `config` - Extra configuration options.
559    pub fn new(
560        mut writer: T,
561        files: &[&str],
562        encoding: Encoding,
563        config: &ExtraConfig,
564    ) -> Result<Self> {
565        writer.write_all(b"PackFile    ")?;
566        let file_count = files.len() as u32;
567        writer.write_u32(file_count)?;
568        let mut headers = HashMap::new();
569        for file in files {
570            let header = BgiFileHeader {
571                filename: file.to_string(),
572                offset: 0,
573                size: 0,
574                _padding: vec![0; 8],
575            };
576            header.pack(&mut writer, false, encoding, &None)?;
577            headers.insert(file.to_string(), header);
578        }
579        Ok(BgiArchiveWriter {
580            writer: Arc::new(Mutex::new(writer)),
581            headers: Arc::new(Mutex::new(headers)),
582            compress_file: config.bgi_compress_file,
583            encoding,
584            compress_level: config.bgi_compress_level,
585            runner: ThreadPool::new(
586                if config.bgi_compress_file {
587                    config.bgi_arc_workers
588                } else {
589                    1
590                },
591                Some("bgi-arc-writer"),
592                false,
593            )?,
594        })
595    }
596}
597
598impl<T: Write + Seek + Send + Sync + 'static> Archive for BgiArchiveWriter<T> {
599    fn new_file<'a>(
600        &'a mut self,
601        name: &str,
602        size: Option<u64>,
603    ) -> Result<Box<dyn WriteSeek + 'a>> {
604        let mut entry = self
605            .headers
606            .lock_blocking()
607            .get(name)
608            .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?
609            .clone();
610        if self.compress_file {
611            let inner = self.new_file_non_seek(name, size)?;
612            Ok(Box::new(Writer {
613                inner,
614                mem: MemWriter::new(),
615            }))
616        } else {
617            let mut writer = self.writer.lock_blocking();
618            let offset = writer.seek(SeekFrom::End(0))?;
619            entry.offset = offset as u32;
620            Ok(Box::new(BgiArchiveFile {
621                header: entry,
622                writer: self.writer.clone(),
623                pos: 0,
624                headers: self.headers.clone(),
625                name: name.to_owned(),
626            }))
627        }
628    }
629
630    fn new_file_non_seek<'a>(
631        &'a mut self,
632        name: &str,
633        _size: Option<u64>,
634    ) -> Result<Box<dyn Write + 'a>> {
635        if !self.compress_file {
636            return Ok(Box::new(self.new_file(name, _size)?));
637        }
638        for err in self.runner.take_results() {
639            err?;
640        }
641        let mut entry = self
642            .headers
643            .lock_blocking()
644            .get(name)
645            .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?
646            .clone();
647        let (reader, writer) = std::io::pipe()?;
648        let file = self.writer.clone();
649        let headers = self.headers.clone();
650        let compress_level = self.compress_level;
651        let name = name.to_owned();
652        self.runner.execute(
653            move |_| {
654                let mut reader = reader;
655                let mut data = Vec::new();
656                reader.read_to_end(&mut data)?;
657                let mut buf = MemWriter::new();
658                {
659                    let mut b = std::io::BufWriter::new(&mut buf);
660                    DscEncoder::new(&mut b, compress_level).pack(&data)?;
661                }
662                let mut writer = file.lock_blocking();
663                let offset = writer.seek(SeekFrom::End(0))?;
664                entry.offset = offset as u32;
665                writer.write_all(&buf.data)?;
666                entry.size = buf.data.len() as u32;
667                headers.lock_blocking().insert(name, entry);
668                Ok(())
669            },
670            true,
671        )?;
672        Ok(Box::new(writer))
673    }
674
675    fn write_header(&mut self) -> Result<()> {
676        self.runner.join();
677        for err in self.runner.take_results() {
678            err?;
679        }
680        let mut writer = self.writer.lock_blocking();
681        let mut headers = self.headers.lock_blocking();
682        writer.seek(SeekFrom::Start(0x10))?;
683        let base_offset = headers.len() as u32 * 0x20 + 16;
684        let mut files = headers.iter_mut().map(|(_, d)| d).collect::<Vec<_>>();
685        files.sort_by_key(|f| f.offset);
686        for file in files {
687            file.offset -= base_offset;
688            file.pack(writer.deref_mut(), false, self.encoding, &None)?;
689        }
690        Ok(())
691    }
692}
693
694/// BGI Archive File Writer (Not compressed)
695pub struct BgiArchiveFile<T: Write + Seek> {
696    header: BgiFileHeader,
697    writer: Arc<Mutex<T>>,
698    pos: usize,
699    headers: Arc<Mutex<HashMap<String, BgiFileHeader>>>,
700    name: String,
701}
702
703impl<T: Write + Seek> Write for BgiArchiveFile<T> {
704    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
705        let mut writer = self.writer.lock_blocking();
706        writer.seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
707        let bytes_written = writer.write(buf)?;
708        self.pos += bytes_written;
709        self.header.size = self.header.size.max(self.pos as u32);
710        Ok(bytes_written)
711    }
712
713    fn flush(&mut self) -> std::io::Result<()> {
714        self.writer.lock_blocking().flush()
715    }
716}
717
718impl<T: Write + Seek> Seek for BgiArchiveFile<T> {
719    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
720        let new_pos = match pos {
721            SeekFrom::Start(offset) => offset as usize,
722            SeekFrom::End(offset) => {
723                if offset < 0 {
724                    if (-offset) as usize > self.header.size as usize {
725                        return Err(std::io::Error::new(
726                            std::io::ErrorKind::InvalidInput,
727                            "Seek from end exceeds file length",
728                        ));
729                    }
730                    self.header.size as usize - (-offset) as usize
731                } else {
732                    self.header.size as usize + offset as usize
733                }
734            }
735            SeekFrom::Current(offset) => {
736                if offset < 0 {
737                    if (-offset) as usize > self.pos {
738                        return Err(std::io::Error::new(
739                            std::io::ErrorKind::InvalidInput,
740                            "Seek from current exceeds current position",
741                        ));
742                    }
743                    self.pos.saturating_sub((-offset) as usize)
744                } else {
745                    self.pos + offset as usize
746                }
747            }
748        };
749        self.pos = new_pos;
750        Ok(self.pos as u64)
751    }
752}
753
754impl<T: Write + Seek> Drop for BgiArchiveFile<T> {
755    fn drop(&mut self) {
756        self.headers
757            .lock_blocking()
758            .insert(self.name.clone(), self.header.clone());
759    }
760}
761
762struct Writer<'a> {
763    inner: Box<dyn Write + 'a>,
764    mem: MemWriter,
765}
766
767impl std::fmt::Debug for Writer<'_> {
768    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
769        f.debug_struct("Writer").field("mem", &self.mem).finish()
770    }
771}
772
773impl<'a> Write for Writer<'a> {
774    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
775        self.mem.write(buf)
776    }
777
778    fn flush(&mut self) -> std::io::Result<()> {
779        self.mem.flush()
780    }
781}
782
783impl<'a> Seek for Writer<'a> {
784    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
785        self.mem.seek(pos)
786    }
787
788    fn stream_position(&mut self) -> std::io::Result<u64> {
789        self.mem.stream_position()
790    }
791
792    fn rewind(&mut self) -> std::io::Result<()> {
793        self.mem.rewind()
794    }
795}
796
797impl<'a> Drop for Writer<'a> {
798    fn drop(&mut self) {
799        let _ = self.inner.write_all(&self.mem.data);
800        let _ = self.inner.flush();
801    }
802}